/* Copyright (C) 2014 InfiniDB, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; version 2 of
   the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA. */

/******************************************************************************
 * $Id: timeset.h 9655 2013-06-25 23:08:13Z xlou $
 *
 *****************************************************************************/

/** @file timeset.h
 * Used to maintain collection of timers tracking elapsed times for set of tasks
 */

#pragma once

#include <ctime>
#include <iostream>
#include <string>
#include <map>

namespace joblist
{
//------------------------------------------------------------------------------
/** @brief Maintains a group of simulated timers and their elapsed times.
 *
 * This class maintains a collection of timers that can be used to collect
 * statistics about the cumulative elapsed time spent on selected tasks.
 */
//------------------------------------------------------------------------------
class TimeSet
{
  // TimerMap maps a timer to its start time
  // ElapsedMap maps a timer to its cumulative elapsed time
  typedef std::map<std::string, timespec> TimerMap;
  typedef std::map<std::string, timespec> ElapsedMap;

 public:
  TimeSet() : fTimer(), fElapsed()
  {
  }
  void displayAll() const;
  void display(const std::string& key) const;
  double totalTime() const;
  double totalTime(const std::string& key) const;
  void holdTimer(const std::string& key);
  void startTimer(const std::string& key);
  void stopTimer(const std::string& key);
  void setTimer(const std::string& key, bool start = true);
  TimeSet& operator+=(const TimeSet& rhs);
  void clear()
  {
    fTimer.clear();
    fElapsed.clear();
  }

 private:
  void timespec_sub(const struct timespec& tv1,  // start time
                    const struct timespec& tv2,  // end time
                    struct timespec& diff) const;
  void timespec_add(const struct timespec& tv1, const struct timespec& tv2, struct timespec& sum) const;

  TimerMap fTimer;
  ElapsedMap fElapsed;
};

//------------------------------------------------------------------------------
// Print the contents (to std::cout) of all timers in the ElapsedMap map.
//------------------------------------------------------------------------------
inline void TimeSet::displayAll() const
{
  ElapsedMap::const_iterator itend = fElapsed.end();

  for (ElapsedMap::const_iterator it = fElapsed.begin(); it != itend; ++it)
  {
    double t;
    t = (double)it->second.tv_sec + (double)it->second.tv_nsec / 1000000000.0;
    std::cout << "TimeSet " << it->first << ": " << t << "s\n";
  }

  std::cout.flush();
}

//------------------------------------------------------------------------------
// Print the specified timer to std::cout.
// If specified timer is in the ElapsedMap map, then the contents of that
// timer will be printed, else if the specified timer is in the TimerMap map,
// then the contents of that timer will be printed.
// key (in) - string that identifies the timer to be printed.
//------------------------------------------------------------------------------
inline void TimeSet::display(const std::string& key) const
{
  ElapsedMap::const_iterator em = fElapsed.find(key);

  if (fElapsed.end() != em)
  {
    double t;
    t = (double)em->second.tv_sec + (double)em->second.tv_nsec / 1000000000.0;
    std::cout << "TimeSet elapse " << em->first << ": " << t << "s\n";
  }
  else
  {
    TimerMap::const_iterator tm = fTimer.find(key);

    if (fTimer.end() != tm)
    {
      double t;
      t = (double)tm->second.tv_sec + (double)tm->second.tv_nsec / 1000000000.0;
      std::cout << "TimeSet start " << tm->first << ": " << t << "s\n";
    }
  }

  std::cout.flush();
}

//------------------------------------------------------------------------------
// Return sum of all timer elapsed times.
//------------------------------------------------------------------------------
inline double TimeSet::totalTime() const
{
  struct timespec tSum = {0, 0};
  ElapsedMap::const_iterator itend = fElapsed.end();

  for (ElapsedMap::const_iterator it = fElapsed.begin(); it != itend; ++it)
  {
  }

  double totSeconds;
  totSeconds = (double)tSum.tv_sec + (double)tSum.tv_nsec / 1000000000.0;

  return totSeconds;
}

//------------------------------------------------------------------------------
// Return current elapsed time for the specified timer.
// key (in) - string that identifies the timer of interest.
//------------------------------------------------------------------------------
inline double TimeSet::totalTime(const std::string& key) const
{
  ElapsedMap::const_iterator el = fElapsed.find(key);

  if (fElapsed.end() != el)
  {
    double totSeconds;
    totSeconds = (double)el->second.tv_sec + (double)el->second.tv_nsec / 1000000000.0;
    return totSeconds;
  }
  else
  {
    return 0;
  }
}

//------------------------------------------------------------------------------
// "Hold" the specified timer.
// Elapsed time is recorded, and added to total elapsed time.
// Unlike stopTimer(), the start time is not reset to current time; instead
// startTimer() or setTimer() must be called by the application code when it
// is time to reset the start time and effectively resume or restart the timer.
// key (in) - string that identifies the timer to be "held".
//------------------------------------------------------------------------------
inline void TimeSet::holdTimer(const std::string& key)
{
  TimerMap::iterator it = fTimer.find(key);

  if (fTimer.end() != it)
  {
    struct timespec tEnd;
    struct timespec tDiff;
#if defined(CLOCK_REALTIME)
    clock_gettime(CLOCK_REALTIME, &tEnd);
#else
    tEnd.tv_sec = tEnd.tv_nsec = 0;
#endif
    timespec_sub(it->second, tEnd, tDiff);
    struct timespec tElapsed = fElapsed[key];
    timespec_add(tElapsed, tDiff, fElapsed[key]);
  }
}

//------------------------------------------------------------------------------
// Start the specified timer.
// Start time is set to current time.
// key (in) - string that identifies the timer to be started.
//------------------------------------------------------------------------------
inline void TimeSet::startTimer(const std::string& key)
{
  struct timespec ts;
#if defined(CLOCK_REALTIME)
  clock_gettime(CLOCK_REALTIME, &ts);
#else
  ts.tv_sec = ts.tv_nsec = 0;
#endif
  fTimer[key] = ts;
}

//------------------------------------------------------------------------------
// Stop the specified timer.
// Elapsed time is recorded, and start time is set to current time.
// key (in) - string that identifies the timer to be stopped.
//------------------------------------------------------------------------------
inline void TimeSet::stopTimer(const std::string& key)
{
  TimerMap::iterator it = fTimer.find(key);

  if (fTimer.end() != it)
  {
    struct timespec tEnd;
    struct timespec tDiff;
#if defined(CLOCK_REALTIME)
    clock_gettime(CLOCK_REALTIME, &tEnd);
#else
    tEnd.tv_sec = tEnd.tv_nsec = 0;
#endif
    timespec_sub(it->second, tEnd, tDiff);
    fElapsed[key] = tDiff;
    fTimer[key] = tEnd;
  }
}

//------------------------------------------------------------------------------
// Start or stop the specified timer.
// key (in)   - string that identifies the timer to be started or stopped.
// start (in) - boolean indicating whether to start or stop the timer.
//------------------------------------------------------------------------------
inline void TimeSet::setTimer(const std::string& key, bool start /*= true*/)
{
  if (start)
    startTimer(key);
  else
    stopTimer(key);
}

//------------------------------------------------------------------------------
// Adds the specified TimeSet elapsed times to "this" TimeSet.
// rhs (in) - TimeSet with elapsed times to be added to "this" TimeSet.
//------------------------------------------------------------------------------
inline TimeSet& TimeSet::operator+=(const TimeSet& rhs)
{
  ElapsedMap::const_iterator itend = rhs.fElapsed.end();

  for (ElapsedMap::const_iterator it = rhs.fElapsed.begin(); it != itend; ++it)
  {
    struct timespec tLhs = fElapsed[it->first];
    timespec_add(tLhs, it->second, fElapsed[it->first]);
  }

  return (*this);
}

//------------------------------------------------------------------------------
// Takes the difference between 2 timespec values.
// tv1  (in)  - first timespec value (subtracted from tv2)
// tv2  (in)  - second timespec value
// diff (out) - value of tv2 - tv1
//------------------------------------------------------------------------------
inline void TimeSet::timespec_sub(const struct timespec& tv1,  // start time
                                  const struct timespec& tv2,  // end time
                                  struct timespec& diff) const
{

  if (tv2.tv_nsec < tv1.tv_nsec)
  {
    diff.tv_sec = tv2.tv_sec - tv1.tv_sec - 1;
    diff.tv_nsec = tv2.tv_nsec + 1000000000 - tv1.tv_nsec;
  }
  else
  {
    diff.tv_sec = tv2.tv_sec - tv1.tv_sec;
    diff.tv_nsec = tv2.tv_nsec - tv1.tv_nsec;
  }

}

//------------------------------------------------------------------------------
// Add 2 timespec values.
// tv1 (in)  - first timespec value
// tv2 (in)  - second timespec value
// sum (out) - sum of tv1 and tv2
//------------------------------------------------------------------------------
inline void TimeSet::timespec_add(const struct timespec& tv1,  // start time
                                  const struct timespec& tv2,  // end time
                                  struct timespec& sum) const
{
  sum.tv_sec = tv1.tv_sec + tv2.tv_sec;
  sum.tv_nsec = tv1.tv_nsec + tv2.tv_nsec;

  if (sum.tv_nsec >= 1000000000)
  {
    sum.tv_sec += 1;
    sum.tv_nsec -= 1000000000;
  }

}

};  // namespace joblist
